package com.tools.commons.response;


import com.tools.commons.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@Slf4j
@ControllerAdvice(basePackages = "com.tools")
public class CommonResultControllerAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(@Nullable MethodParameter returnType,
                            @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
        log.info("support {}", returnType);
        return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType)
                || StringHttpMessageConverter.class.isAssignableFrom(converterType);
    }

    @Override
    @NonNull
    public final Object beforeBodyWrite(@Nullable Object returnBody,
                                        @Nullable MethodParameter returnType,
                                        @Nullable MediaType contentType,
                                        @Nullable Class<? extends HttpMessageConverter<?>> converterType,
                                        @Nullable ServerHttpRequest request,
                                        @NonNull ServerHttpResponse response) {
        Result<?> baseResponse;
        if (returnBody instanceof Result) {
            baseResponse = (Result<?>) returnBody;
        } else {
            baseResponse = R.ok(returnBody);
        }

        if (returnBody instanceof String || returnBody == null) {
            // SpringMvc 内部定义了九个不同的 MessageConverter 用来处理不同的返回值。
            // 在 AbstractMessageConverterMethodProcessor 类下面的 writeWithMessageConverters 方法可以看出来，
            // 每个 MessageConverter 是根据返回类型和媒体类型来选择处理的 MessageConverter 的
            // controller 层中返回的类型是 String，
            // 但是在 ResponseBodyAdvice 实现类中，我们把响应的类型修改成了 Result。
            // 这就导致了，上面的这段代码在选择处理 MessageConverter 的时候，
            // 依旧根据之前的 String 类型选择对应 String 类型的 StringMessageConverter。
            // 而在 StringMessageConverter 类型，
            // 他只接受 String 类型的返回类型，
            // 我们在 ResponseBodyAdvice 中将返回值从 String 类型改成 Result 类型之后，
            // 调用 StringMessageConverter  方法发生类型强转。
            // Result 无法转换成 String，发生类型转换异常。
            // 所以解决该异常最好的方式就是重写 StringMessageConverter 方法，
            // 让他可以解决 Result 类型的转化。这种方式相对比较麻烦，

            // 所以换了一种解决方式，
            // 在 ResponseBodyAdvice 中做了针对 String 类型返回值的修改
            return JsonUtil.obj2String(baseResponse);
        }

        response.setStatusCode(HttpStatus.valueOf(baseResponse.getCode()));
        MappingJacksonValue container = getOrCreateContainer(returnBody);
        container.setValue(baseResponse);
        return container;
    }

    private MappingJacksonValue getOrCreateContainer(Object body) {
        return body instanceof MappingJacksonValue ? (MappingJacksonValue) body : new MappingJacksonValue(body);
    }
}
